#include "StdAfx.h"

#include "Floor.h"
#include "Elevator.h"
#include <algorithm>

CElevator::CElevator(size_t capacity) : 
	m_capacity(capacity),  
	m_actionListener(NULL), 
	m_upDirection(true), 
	m_curFloor(0), 
	m_floorsContainer(NULL), 
	m_updateListener(NULL), 
	m_transportedListener(NULL),
	m_loadPassengersListener(NULL),
	m_avgWaitTime(0)
{
	m_passengers.reserve(capacity);
}

CElevator::~CElevator(void)
{
	if (m_updateListener != NULL)
		delete m_updateListener;

	if (m_actionListener != NULL)
		delete m_actionListener;

	if (m_transportedListener != NULL)
		delete m_transportedListener;

	if (m_loadPassengersListener != NULL)
		delete m_loadPassengersListener;
}

void CElevator::SetFloorsContainer(IFloorsContainer* container)
{
	if (container == NULL)
		throw std::runtime_error("FloorsContainer not set");

	m_floorsContainer = container;
	FloorsCollection floors = m_floorsContainer->GetFloors();
	for (FloorsCollection::iterator it = floors.begin(); it != floors.end(); ++it)
	{
		CFloor* curFloor = *it;
		curFloor->RegisterListeners(
			new CIncomingPassengersListener(*this), 
			new CallRequestlistener(*this)
		);
	}

	m_waitingData.resize(container->GetFloors().size(), 0);
}

void CElevator::SetUpdateListener(IUpdateListener* l)
{
	m_updateListener = l;
}

void CElevator::SetActionListener(IElevatorActionListener* l)
{
	m_actionListener = l;
}

void CElevator::SetTransportedListener(IPassengertransportedListener* l)
{
	m_transportedListener = l;
}

void CElevator::SetIncomingPassengersListener(ILoadPassengersListener* l)
{
	m_loadPassengersListener = l;
}

void CElevator::MoveUp()
{
	if(m_curFloor < m_floorsContainer->GetFloors().size() - 1)
	{
		++m_curFloor;
		m_upDirection = true;
		FireAction();
	}
}

void CElevator::MoveDown()
{
	if(m_curFloor > 0)
	{
		--m_curFloor;
		m_upDirection = false;
		FireAction();
	}
}

void CElevator::StartMove()
{
	while (!m_currentCallRequests.empty() || !m_passengers.empty())
	{
		if (m_floorsContainer->ShouldStop())
			break;

		if (!m_passengers.empty())
		{
			CPassenger* pas = *m_passengers.begin();
			if (m_curFloor > pas->GetTarget())
				MoveDown();
			else if (m_curFloor < pas->GetTarget())
				MoveUp();
			else
				m_upDirection = !m_upDirection;
		}
		else if (!m_currentCallRequests.empty())
		{
			CallRequest req = *m_currentCallRequests.begin();
			if (m_curFloor > req.fromFloor)
				MoveDown();
			else if (m_curFloor < req.fromFloor)
				MoveUp();
			else
				m_upDirection = !m_upDirection;
		}

		// arrived passengers are trying to exit
		UnloadPassengers();

		//incoming passengers are trying to enter
		//WaitForElevator
		m_floorsContainer->GetFloors()[m_curFloor]->ElevatorArrived();

		// if elevator has call-request from this floor to a right direction the request is completed
		UpdateCalls();
		
		// next passenger from previous floor calls the elevator
		m_floorsContainer->GetFloors()[m_curFloor]->ElevatorLeft();

		if (m_updateListener != NULL)
			m_updateListener->Update();
	}
}

void CElevator::UnloadPassengers()
{
	for (PassengerCollection::iterator it = m_passengers.begin(); it != m_passengers.end(); ++it)
		(*it)->NewFloor(m_curFloor); //CPassengerArrivedListener should be notified here

	for (std::vector<size_t>::reverse_iterator it = m_passengerToExit.rbegin(); it != m_passengerToExit.rend(); ++it)
	{
		m_passengers.erase(m_passengers.begin() + *it);
		if (m_transportedListener != NULL)
			m_transportedListener->OnPassengerTransported();
	}

	m_passengerToExit.clear();
}

void CElevator::UpdateCalls()
{
	//updating current calls registry and taking some of incoming into work
	bool found = false;
	for(CallRequestRegistry::const_iterator it = m_currentCallRequests.begin(); it != m_currentCallRequests.end(); ++it)
	{
		found = (it->fromFloor == m_curFloor) && (it->upDirection == m_upDirection);
		if (found)
		{
			m_currentCallRequests.erase(it);
			break;
		}
	}

	if (!found)
		return;

	if (m_incomingCallRequests.empty())
		return;// no incoming calls

	// all current requests were completed, trying to handle incoming ones
	for (CallRequestRegistry::const_iterator it = m_incomingCallRequests.begin(); it != m_incomingCallRequests.end(); ++it)
		m_currentCallRequests.push_back(*it);

	m_incomingCallRequests.clear();
}

void CElevator::FireAction()
{
	if (m_actionListener != NULL)
		m_actionListener->OnElevatorAction();
}

void CElevator::PassengersLoaded()
{
	if (m_loadPassengersListener != NULL)
		m_loadPassengersListener->LoadPassengersAtArival(m_passengers);
}

void CElevator::RecalculateAvgWaitTime()
{
	for (size_t i=0; i<m_floorsContainer->GetFloors().size(); ++i)
	{
		CFloor* floor = m_floorsContainer->GetFloors().at(i);
		if (i == GetElevatorPos())
			m_waitingData[i] = floor->GetStepsWaited();
		else
			floor->WaitForElevator();
	}

	m_avgWaitTime = 0;
	for (size_t i=0; i<m_waitingData.size(); ++i)
		m_avgWaitTime += m_waitingData[i];

	m_avgWaitTime = m_avgWaitTime / m_waitingData.size();
}
/////////////////////////////////////////////////////////////////////////////////////////////////
void CElevator::CPassengerArrivedListener::OnPassengerArrived(CPassenger* passenger)
{
	m_elevator.m_passengerToExit.reserve(m_elevator.m_passengers.size());
	for (size_t i=0; i<m_elevator.m_passengers.size(); ++i)
	{
		if (m_elevator.m_passengers[i] == passenger)
			m_elevator.m_passengerToExit.push_back(i);
	}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
void CElevator::CIncomingPassengersListener::LoadPassengersAtArival(PassengerCollection& incomingPassengers)
{
	size_t numAdded(0);
	for (PassengerCollection::iterator it = incomingPassengers.begin(); it != incomingPassengers.end(); ++it)
	{
		if (m_elevator.m_capacity == m_elevator.m_passengers.size())
			break;

		CPassenger* p = static_cast<CPassenger*>(*it);
		p->RegisterListener(new CPassengerArrivedListener(m_elevator));
		m_elevator.m_passengers.push_back(p);
		++numAdded;
	}

	if (numAdded > 0)
	{
		incomingPassengers.erase(incomingPassengers.begin(), incomingPassengers.begin() + numAdded);
		m_elevator.FireAction();
		m_elevator.PassengersLoaded();
		m_elevator.RecalculateAvgWaitTime();
	}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
void CElevator::CallRequestlistener::OnCallRequest(size_t from, bool upDirection)
{
	if (m_elevator.m_currentCallRequests.empty())
		m_elevator.m_currentCallRequests.push_back(CallRequest(from, upDirection));
	else
	{
		CallRequest r(from, upDirection);
		CallRequestRegistry& current = m_elevator.m_currentCallRequests;
		CallRequestRegistry& incoming = m_elevator.m_incomingCallRequests;
		if ((std::find(incoming.begin(), incoming.end(), r) == incoming.end()) &&
			(std::find(current.begin(), current.end(), r) == current.end()))
		{
			m_elevator.m_incomingCallRequests.push_back(r);
		}
	}
}